// TODO: CODE REVIEW & CLEANUP!
//
// TouchSlider.cs
//
// Implements TouchSlider and related types.
//

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using HomeUX.ClientControlSupport;
using HomeUX.Synchronization;
using SyncPathClass = HomeUX.Synchronization.SyncPath;

namespace HomeUX.Controls
{

[TemplatePart(Name = LayoutRootName, Type = typeof(Panel))]
[TemplatePart(Name = OuterThumbName, Type = typeof(Control))] // e.g. TouchShadow
[TemplatePart(Name = InnerThumbName, Type = typeof(TouchSliderThumb))]
public class TouchSlider : RangeBase, IControlHelperConnector
{
    const string LayoutRootName = "LayoutRoot";
    const string OuterThumbName = "OuterThumb";
    const string InnerThumbName = "InnerThumb";

    public static readonly DependencyProperty ThumbSizeProperty =
        DependencyProperty.Register("ThumbSize", typeof(double), typeof(TouchSlider),
            new PropertyMetadata(TouchSlider.OnThumbSizePropertyChanged));
    public static readonly DependencyProperty IsDirectionReversedProperty =
        DependencyProperty.Register("IsDirectionReversed", typeof(bool), typeof(TouchSlider),
            new PropertyMetadata(TouchSlider.OnIsDirectionReversedPropertyChanged));
    public static readonly DependencyProperty IsThumbTabStopProperty =
        DependencyProperty.Register("IsThumbTabStop", typeof(bool), typeof(TouchSlider),
        null);
    public static readonly DependencyProperty IsWaitingProperty =
        DependencyProperty.Register("IsWaiting", typeof(bool), typeof(TouchSlider),
            new PropertyMetadata(TouchSlider.OnIsWaitingPropertyChanged));
    public static readonly DependencyProperty SyncPathProperty =
        DependencyProperty.Register("SyncPath", typeof(string), typeof(TouchSlider),
            new PropertyMetadata(TouchSlider.OnSyncPathPropertyChanged));

    Panel _layoutRoot;
    Control _outerThumb;
    TouchSliderThumb _innerThumb;

	// the smallest height for <_innerThumb>, or 0 if it hasn't been set yet -- this height is
	// defined by the initial height of <_innerThumb>
	double _innerThumbMinimumHeight;

    /// <summary>
    /// Implements the connection between this control and the global SyncTree object, which in
    /// turn manages the connection to the server.
    /// </summary>
    SyncHelper _syncHelper;

    public event IsWaitingChangedEventHandler IsWaitingChanged;

    public double ThumbSize
    {
        get
        {
            return (double)GetValue(ThumbSizeProperty);
        }
        set
        {
            SetValue(ThumbSizeProperty, value);
        }
    }

    public bool IsDirectionReversed
    {
        get
        {
            return (bool)GetValue(IsDirectionReversedProperty);
        }
        set
        {
            SetValue(IsDirectionReversedProperty, value);
        }
    }

    public bool IsThumbTabStop
    {
        get
        {
            return (bool)GetValue(IsThumbTabStopProperty);
        }
        set
        {
            SetValue(IsThumbTabStopProperty, value);
        }
    }

    public bool IsWaiting
    {
        get
        {
            return (bool)GetValue(IsWaitingProperty);
        }
        set
        {
            SetValue(IsWaitingProperty, value);
        }
    }

    public string SyncPath
    {
        get
        {
            return (string)GetValue(SyncPathProperty);
        }
        set
        {
            SetValue(SyncPathProperty, value);
        }
    }

    public TouchSlider() 
    {
        DefaultStyleKey = typeof(TouchSlider);

        _syncHelper = new SyncHelper(this, 0);

        SizeChanged += delegate
        {
            _innerThumb.ApplyValue(Value);
        };

        //TODO: why is this here?
        ValueChanged += delegate
        {
            return;
        };

		KeyDown += TouchSlider_KeyDown;

        // hook up <_syncHelper> events
        _syncHelper.IsOnlineChanged += _syncHelper_IsOnlineChanged;
        _syncHelper.IsWaitingChanged += _syncHelper_IsWaitingChanged;
        _syncHelper.PrimaryValueChanged += _syncHelper_PrimaryValueChanged;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        ControlHelper.GetRequiredTemplateChild(this, LayoutRootName, out _layoutRoot);
        ControlHelper.GetRequiredTemplateChild(this, OuterThumbName, out _outerThumb);
        ControlHelper.GetRequiredTemplateChild(this, InnerThumbName, out _innerThumb);

        // check if this control is an orphan when Parent may change
        //_syncHelper.CheckOrphanStatus(Parent);
        LayoutUpdated += (sender, e) => _syncHelper.CheckOrphanStatus(Parent);

        // set up a connection from <_innerThumb> back to <this>
        _innerThumb.SetTouchSlider(this);
	}

    /// <summary>
    /// Called when <r>_syncHelper.PrimaryNode.Value</r> has changed -- for example, if another client has
    /// changed the value.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_PrimaryValueChanged(object sender, ValueChangedEventArgs e)
    {
        // ignore this event if the change originated from this control
        if (e.Originator == this)
            return;

        // Ignore this event if an hourglass is displayed, to deal with this scenario:
        //   Imagine the user is dragging a sound volume slider, but the audio hardware is
        //   slow to respond, so one or more volume changes get queued up.  So this is what
        //   the user sees: they drag the slider from (e.g.) top (100%) to bottom (0%), and
        //   then over the next couple of seconds they see the slider jump back up to 75%, then
        //   down to 50%, then down to 25%, then to 0% (for example).  This is disconcerting.
        if (_syncHelper.IsWaiting)
            return;

        // apply the new value in <_syncHelper.PrimaryNode.Value> to <Value>
        object newValue = _syncHelper.PrimaryNode.Value;
        if (newValue is double)
            Value = (double)newValue;
    }

    /// <summary>
    /// Called when <r>_syncHelper.IsWaiting</r> has changed.  This occurs when
    /// <r>_syncHelper.PrimaryNode.Value</r> is changed on this client, but the change has not yet been
    /// reflected on the server.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_IsWaitingChanged(object sender, EventArgs e)
    {
        // show or hide the wait icon (e.g. hourglass)
        IsWaiting = _syncHelper.IsWaiting;
    }

    /// <summary>
    /// Called when <r>_syncHelper.IsOnline</r> has changed.  This occurs when the connection to
    /// the server has been broken or reestablished.  This also occurs when
    /// <r>_syncHelper.PrimaryNode.Value</r> becomes <n>null</n> (indicating, for example, that the device
    /// associated with that value is offline) or becomes non-<n>null</n>.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_IsOnlineChanged(object sender, EventArgs e)
    {
        // enable or disable the control
        IsEnabled = _syncHelper.IsOnline;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        Size size = base.ArrangeOverride(finalSize);

		// initialize <_innerThumbMinimumHeight> if it hasn't been initialized yet
		if (_innerThumbMinimumHeight == 0)
			_innerThumbMinimumHeight = _innerThumb.ActualHeight;

		// the amount of available space for the thumb is determined by the layout slot of
		// <_outerThumb>, but to change the height of the thumb we change <_innerThumb.Height>;
		// set <dy> to the distance between the two
        double dy = _outerThumb.ActualHeight - _innerThumb.ActualHeight;

		// set <availableHeight> to the maximum height of <_innerThumb>
        var rect = LayoutInformation.GetLayoutSlot(_outerThumb);// xx zz
        double availableHeight = rect.Height - dy;

		// calculate the new height of <_innerThumb>; bound ThumbSize to [0,1] 
        _innerThumb.Height = Math.Max(_innerThumbMinimumHeight,
			availableHeight * Math.Max(0, Math.Min(1, ThumbSize)));

		// return what base.ArrangeOverride calculated
        return size;
    }

    protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
    {
        base.OnMinimumChanged(oldMinimum, newMinimum);
        _innerThumb.ApplyValue(Value);
    }

    protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
    {
        base.OnMaximumChanged(oldMaximum, newMaximum);
        _innerThumb.ApplyValue(Value);
    }

    static void OnThumbSizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchSlider)d;
        target.InvalidateArrange();
    }

    static void OnSyncPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchSlider)d;
        target._syncHelper.SyncPath = SyncPathClass.FromString((string)e.NewValue);
    }

    static void OnIsWaitingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchSlider)d;
        target._innerThumb.IsWaiting = (bool)e.NewValue;

        // fire the IsWaitingChanged event
        if (target.IsWaitingChanged != null)
        {
            target.IsWaitingChanged(target, new IsWaitingChangedEventArgs()
            {
                IsWaiting = (bool)e.NewValue
            });
        }
    }

    protected override void OnValueChanged(double oldValue, double newValue)
    {
        base.OnValueChanged(oldValue, newValue);

        // update UI
        if (_innerThumb != null) // i.e. if OnApplyTemplate was called yet
            _innerThumb.ApplyValue(newValue);

        // notify the SyncTree of the change
        if (_syncHelper.IsPrimaryNodeAvailable)
            _syncHelper.PrimaryNode.SetValue(newValue, this);
    }

    static void OnIsDirectionReversedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchSlider)d;
    }

    void TouchSlider_KeyDown(object sender, KeyEventArgs e)
    {
        if (!e.Handled)
        {
            e.Handled = OnKeyDown(e.Key);
        }
    }

    internal bool OnKeyDown(Key key)
    {
        switch (key)
        {

            case Key.Left:

                Value -= SmallChange;
                break;

            case Key.Right:

                Value += SmallChange;
                break;

            case Key.Up:

                Value += IsDirectionReversed ? SmallChange : -SmallChange;
                break;

            case Key.Down:

                Value += IsDirectionReversed ? -SmallChange : SmallChange;
                break;

            case Key.PageUp:

                Value += IsDirectionReversed ? LargeChange : -LargeChange;
                break;

            case Key.PageDown:

                Value += IsDirectionReversed ? -LargeChange : LargeChange;
                break;

            case Key.Home:

                Value = IsDirectionReversed ? Maximum : Minimum;
                break;

            case Key.End:

                Value = IsDirectionReversed ? Minimum : Maximum;
                break;

            default:

                return false;

        }

        return true;
    }

	// IControlHelperConnector implementation

    Control IControlHelperConnector.GetControl()
    {
        return this;
    }

    DependencyObject IControlHelperConnector.GetTemplateChild(string childName)
    {
        return GetTemplateChild(childName);
    }
}

public class TouchSliderThumb : TouchButton
{
    Point _dragStartPoint;
    double _dragStartThumbY;
    bool _isDragging;
    Control _outerThumb;
    Panel _outerThumbParent;
    TouchSlider _touchSlider;

    // called by the containing TouchSlider to set up a connection from this TouchSliderThumb to it;
    // this gets called before TouchSliderThumb.OnApplyTemplate
    internal void SetTouchSlider(TouchSlider touchSlider)
    {
        _touchSlider = touchSlider;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        _outerThumb = (Control) Parent;
        _outerThumbParent = (Panel)_outerThumb.Parent;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        return base.ArrangeOverride(finalSize);
    }

    protected override void OnIsPressedChanged(DependencyPropertyChangedEventArgs e)
    {
        //ControlHelper.WriteToDebugLog("IsPressed: {0}", e.NewValue);
        if (_isDragging)
            IsPressed = true;
        base.OnIsPressedChanged(e);
    }

    protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
    {
        _isDragging = true;
        _dragStartPoint = e.GetPosition(_outerThumbParent);
        _dragStartThumbY = _outerThumb.Margin.Top;
        //ControlHelper.WriteToDebugLog("Down: {0}", _dragStartPoint);
        base.OnMouseLeftButtonDown(e);
    }

    protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)
    {
        _isDragging = false;
        //ControlHelper.WriteToDebugLog("Up: {0}", e.GetPosition((UIElement)Parent));
        base.OnMouseLeftButtonUp(e);
    }


    protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
    {
        if (_isDragging)
        {
            Point point = e.GetPosition(_outerThumbParent);
            double deltaY = point.Y - _dragStartPoint.Y;
            double maxThumbY = GetMaxThumbY();
            double newThumbY = Math.Max(0, Math.Min(maxThumbY, _dragStartThumbY + deltaY));

            double newValue = newThumbY / maxThumbY;
			if (_touchSlider.IsDirectionReversed)
				newValue = 1 - newValue;
			newValue = newValue * (_touchSlider.Maximum - _touchSlider.Minimum) + _touchSlider.Minimum;

            ApplyValue(newValue);
            
            _touchSlider.Value = newValue;

            // the following code works around this bug: in ControlHelper.IsTouchMode, if you drag
            // the thumb quickly up and down it can switch to its un-pressed state
            IsPressed = true;
            VisualStateManager.GoToState(this, "Pressed", false);

        }
        base.OnMouseMove(e);
    }

    internal void ApplyValue(double value)
    {
        double maxThumbY = GetMaxThumbY();
        double newThumbY = (value - _touchSlider.Minimum) /
			(_touchSlider.Maximum - _touchSlider.Minimum);
        if (_touchSlider.IsDirectionReversed)
			newThumbY = 1 - newThumbY;
		newThumbY *= maxThumbY;

        Thickness margin = _outerThumb.Margin;
        _outerThumb.Margin = new Thickness(margin.Left, newThumbY, margin.Right, margin.Bottom);
    }

    // returns the maximum value of _outerThumb.Margin.Top (the minimum value is zero)
    double GetMaxThumbY()
    {
        var rect = LayoutInformation.GetLayoutSlot(_outerThumb);
        return rect.Height - _outerThumb.ActualHeight;
    }
}

}

